"""Minerva wsgi resource handlers.

This is the entry point for the system.
"""

import import_wrapper

import datetime
import logging
import wsgiref.handlers

import yaml as pyyaml
# Django-schmango
from django.utils import html
from django.utils import simplejson
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import login_required

import render
import core
# Load in ContentType plugins.
core.registry.Configure('content_types/registry.yaml')

# Load in Site config
SITE_CONFIG = pyyaml.load(open('site.yaml'))

DEBUG = True

SLASH = core.SLASH
CONTENT_TYPE = 'content_type'
TITLE = 'title'
FORMAT = 'format'
EDITOR = 'editor'
COMMA = ','
JSON = 'json'
XML = 'xml'

# Values for respective Content-Type response header
CT_XML = 'text/xml; charset=utf-8'
CT_JSON = 'application/json; charset=utf-8'


class RequestHandler(webapp.RequestHandler):
  """Common functionality for all Minerva RequestHandlers."""

  def generate(self, template_name, **template_values):
    """Convention for wrapping template interpolation machinery.

    @arg template_name: str; Filename of template to use in rendering.
    @arg template_values: dict; Values to pass to rendering.
    """
    req = self.request
    values = dict(
      req=req,
      user=users.GetCurrentUser(),
      login_url=users.CreateLoginURL(req.uri),
      logout_url=users.CreateLogoutURL(req.uri))
    values.update(SITE_CONFIG)
    values.update(template_values)
    output = render.Render('templates', template_name, **values)
    self.response.out.write(output)


class EventsPageHandler(RequestHandler):
  """Handles requests to /evs resource.

  View into all events related to User, possibly
  limited by 'since' query argument.
  """
  per_req_limit = 50

  @login_required
  def get(self):
    """Retrieve representation of /evs.

    Will render as JSON if specified by 'format' query argument,
    HTML otherwise.
    """
    user = users.GetCurrentUser()
    req = self.request
    last_pwun = self.request.get('since')

    evs = core.NounEvent.Since(last_pwun, user, limit=self.per_req_limit)
    number_remaining = evs.count() - self.per_req_limit
    logging.info('number_remaining? %d', number_remaining)

    format = req.get(FORMAT)
    if format == JSON:
      dumps = simplejson.dumps
      buflist = ['{"number_remaining":%d, "events":[' % number_remaining]
      for e in evs:
        buflist.append(dumps(e.meta))
        buflist.append(COMMA)
      buflist.append(']}')
      return self.response.out.write(''.join(buflist))

    return self.generate('events.html', evs=list(evs))


class ContentTypesRequestHandler(RequestHandler):
  """Handles requests to /content_types resource.

  A ContentType plugin is retrievable as either a menu item
  or an editor for creating a new Content instance.

  Under /content_types/ they display as a group (menu). But each can
  respectively be retrieved via /content_types/{name}.

  When one goes to an unoccupied location in the noun space and hits
  'Select something' the menu displayed is actually pulled as HTML
  from /content_types/. This technique is known as AHAH.
  """

  @login_required
  def get(self, name):
    """Retrieve either the content type menu or an editor for a specific one.

    @arg name: str; Name of content type to retrieve. Can also be '/',
        which specifies all of them as a menu.
    """
    if not name or name == SLASH:
      return self.generate('content_types.html', registry=core.registry)

    handler = core.registry.Get(name)
    if not handler:
      return self.error(404)

    editor = self.request.get(EDITOR)
    content_type_html = handler.Html(content=None, editor=editor)
    return self.response.out.write(content_type_html)


class Nada(RequestHandler):
  """Handles requests to locations where we do not want Nouns to be placed.
  """

  def get(self):
    return self.error(404)


class NounSpaceRequestHandler(RequestHandler):
  """Handles all requests into the 'Noun Space'."""
  template_name = 'noun.html'

  @login_required
  def get(self, path):
    """Retrieve content at path.

    If there is already a Noun at this location return representation
    (XML or JSON if specified by 'format' query argument, HTML otherwise)
    of Content instance it links to.

    If no Noun exists here render default page that presents user
    with an option to create a new instance of a supported ContentType.

    @arg path: str; URL path
    """
    user = users.GetCurrentUser()
    req = self.request
    content_html = None
    noun = core.Noun.Find(path)
    if noun:
      # Ensure user has rights to view.
      content = noun.content
      format = req.get(FORMAT)
      if format == JSON:
        content_json = content.Json(req=req)
        self.response.content_type = CT_JSON
        return self.response.out.write(content_json)
      elif format == XML:
        content_xml = content.Xml(req=req)
        self.response.content_type = CT_XML
        return self.response.out.write(content_xml)

      user_is_author = content.author == user
      # Edit only allowed to author or coauthors
      editor = req.get(EDITOR) and user_is_author
      content_html = content.Html(
          req=req, editor=editor, user_is_author=user_is_author)
      if editor:
        return self.response.out.write(content_html)

    return self.generate(self.template_name, content_html=content_html)

  def post(self, path):
    """Create or modify Content pointed to from path.

    @arg path: str; URL path
    """
    user = users.GetCurrentUser()
    # Do we have to check if user has authed here?

    if not path:
      path = SLASH

    req = self.request
    if set([CONTENT_TYPE, TITLE]).difference(req.params.keys()):
      return self.error(400) # Bad Request

    # Copy and clean the POST params.
    params = dict(
        (html.escape(str(key)), html.escape(val.strip()))
        for key, val in req.params.iteritems())

    content_type_name = params.pop(CONTENT_TYPE)
    title = params.pop(TITLE)
    if not title:
      title = ' '.join([seg for seg in path.split(SLASH) if seg])

    noun = core.Noun.Find(path)
    if noun:
      # If there is already a Noun here we are modifying the
      # Content it points to.
      content = noun.content
      user_is_author = content.author == user
      # We don't allow non authors to modify an existing piece of Content
      if not user_is_author:
        return self.error(403) # Forbidden yo

      # Update content
      handler = core.registry.Get(content.type)
      if content.title != title:
        # Don't let User assign the same title to more than one piece
        # of Content
        existing = handler.Find(content.author, title)
        if existing.count():
          return self.error(409) # Conflict

        content.title = title

      handler.Customize(content, **params)
      content.put()

    else:
      # If there is no Noun here yet we are creating one.
      # Ensure that a Content instance does not already exist by this:
      # type/author/title
      handler = core.registry.Get(content_type_name)
      existing = handler.Find(user, title)
      if existing.count():
        return self.error(409) # Conflict

      # Create new Noun and Content
      content = handler.New(author=user, title=title, **params)
      noun = core.Noun.New(creator=user, path=path, content=content)
      noun.ShowTo(core.GLOB) # Share to everyone for now
      user_is_author = True

    content_html = content.Html(
        req=self.request, user_is_author=user_is_author)
    return self.response.out.write(content_html)

  @login_required
  def delete(self, path):
    """Delete the resource (noun and content) if user is author

    @arg path: str; URL path.
    """
    if not path:
      path = SLASH

    req = self.request
    user = users.GetCurrentUser()

    noun = core.Noun.Find(path)
    if noun:
      content = noun.content
      user_is_author = content.author == user
      # We don't allow non authors to modify an existing piece of content
      if not user_is_author:
        return self.error(403) # Forbidden yo

      # Destroy noun (and content for now)
      core.Noun.Destroy(noun)


def main():
  app = webapp.WSGIApplication([
    ('/content_types/(\w*)', ContentTypesRequestHandler),
    ('/content_types.*', Nada),
    ('/evs', EventsPageHandler),
    ('/evs.+', Nada),
    ('(.*)', NounSpaceRequestHandler),
    ], debug=DEBUG)
  wsgiref.handlers.CGIHandler().run(app)


if __name__ == '__main__':
  main()
